#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <malloc.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h> 
#include <sys/select.h>
#include <pthread.h>
#include <linux/videodev2.h>

#include "c270.h"

#include <android/log.h>

#define INIT_ID 		0xFE567DA0
#define TAG "MyNdk" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

typedef struct tagSoV4l2Buffer 
{
	void*  	start;
	size_t  length;
}SoV4l2Buffer;

static struct SoVCData
{
	unsigned int 	init_id;
	char 			device_file_name[32];
	SOCAM_STATUS   	status;
	int 			stopped;
	
	int 			camera_fd;
	
	SoV4l2Buffer*  	camera_buffers;
	unsigned int    camera_buffer_num; 
	
	pthread_t 		video_capture_id;
	FN_CAPTURE_CALLBACK fn_capture;
	FN_ERROR_CALLBACK fn_error;
	void*			user_data;
}socam_data;
						
static int is_file_exist(const char* file_name)
{
    struct stat namestat;

    if(file_name == NULL || strlen(file_name) == 0)
        return 0;

    if(stat(file_name, &namestat) == -1)
    {
        LOGI("===========stat failed ===============");
        return 0;
    }

    return 1;
}

int find_c270_cam_device()
{
	int i =0;
	if(!is_file_exist("/sys/class/video4linux"))
	{
	    LOGI("/sys/class/video4linux file not exist???");
		return -1;
	}
	for(i=0; i<16; i++)
	{
		char path[64] = {0};
		sprintf(path, "/sys/class/video4linux/video%d", i);
		if(is_file_exist(path))
		{
			char device_name[32]= {0};
			sprintf(path, "/sys/class/video4linux/video%d/name", i);
			FILE *fpName = fopen(path, "rb");
            LOGI("the videoname is %s", path);
			if(fpName)
			{
                LOGI("fopen success!!!, the videoname is %s", path);
				fgets(device_name, 31, fpName);
                LOGI("the device name is %s", device_name);
				if(strstr(device_name, "Webcam gadget") != NULL)
				{
				    LOGI("device_index=%d",i);
					return i;
				}
				fclose(fpName);
			}
		}	
	}
	LOGI("没有c270设备！！");
	return -1;
}	

static int xioctl(int fd, int request,void *arg)
{
    int r;

    do r = ioctl (fd, request, arg);
    while (-1 == r && EINTR == errno);

    return r;
}

SOERR c270_init()
{
	int device_index = -1;
	
	if(socam_data.init_id == INIT_ID)
		return SOERR_NONE;
		
	memset(&socam_data, 0, sizeof(socam_data));	
	
	device_index = find_c270_cam_device();
	if(device_index<0)
	{
		LOGI("the c270 device is not been pluged=%d\n", __LINE__);
		return SOERR_NO_CAM_DEVICE;
	}
	
	sprintf(socam_data.device_file_name, "/dev/video%d", device_index);

    LOGI("%s", socam_data.device_file_name);
	if(!is_file_exist(socam_data.device_file_name))
	{
		LOGI("the c270 device is not been pluged=%d\n", __LINE__);
		return SOERR_NO_CAM_DEVICE;
	}

	socam_data.status = SOCAM_STATUS_STOPPED;
	socam_data.init_id = INIT_ID;
	
    return SOERR_NONE;
}

static void* video_capture_thread(void* param)
{
    struct v4l2_buffer buf;
    enum v4l2_buf_type type;
    int i = 0;
    
    param = param;
    socam_data.status = SOCAM_STATUS_RUNNING;
    socam_data.stopped = 0;
    while(socam_data.status == SOCAM_STATUS_RUNNING)
    {
		fd_set fds;
		struct timeval tv;
		int r;

		FD_ZERO (&fds);
		FD_SET (socam_data.camera_fd, &fds);
		tv.tv_sec = 2;
		tv.tv_usec = 0;
		r = select (socam_data.camera_fd + 1, &fds, NULL, NULL, &tv);
		if (-1 == r) 
		{
			if (EINTR == errno)
				continue;
			if(socam_data.fn_error)
				socam_data.fn_error((char*)"select error\n");
			printf ("select errXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
			break;
        }
		if (0 == r) 
		{
			if(socam_data.fn_error)
				socam_data.fn_error((char*)"select timeout\n");
			printf ("select timeoutXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
			break;
		}
		
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		if (-1 == ioctl (socam_data.camera_fd, VIDIOC_DQBUF, &buf)) 
		{
			if(socam_data.fn_error)
				socam_data.fn_error((char*)"failed dqbuf\n");
			break;
		}
		if(socam_data.fn_capture)
		{
			socam_data.fn_capture(socam_data.camera_buffers[buf.index].start, buf.bytesused, 0, socam_data.user_data);
		}
		if (-1 == xioctl (socam_data.camera_fd, VIDIOC_QBUF, &buf))
		{
			if(socam_data.fn_error)
				socam_data.fn_error((char*)"failed qbuf\n");
			break;
		}
	}
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (-1 == xioctl (socam_data.camera_fd, VIDIOC_STREAMOFF, &type))
	{
        printf("VIDIOC_STREAMOFF failed\n");
	}
	socam_data.status = SOCAM_STATUS_STOPPED;
	for (i = 0; i < socam_data.camera_buffer_num; ++i)
	{
		munmap (socam_data.camera_buffers[i].start, socam_data.camera_buffers[i].length);
	}
	free(socam_data.camera_buffers);
	socam_data.camera_buffers = NULL;
	socam_data.stopped = 1;
	pthread_exit(0);
    //printf("exit video capture thread\n");
	return NULL;
}

static SOERR vc_set_res(VideoRes* video_res)
{
    struct v4l2_format fmt;
	
	if(video_res == NULL)
	{
		return SOERR_INVALID_PARAM;
	}
	
	if(socam_data.init_id != INIT_ID)
	{
		return SOERR_UNINITED;
	}
	
	if(socam_data.status == SOCAM_STATUS_RUNNING)
	{
		return SOERR_WRONG_STATUS;
	}	
	
	memset(&fmt, 0, sizeof(struct v4l2_format));
	
    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = video_res->width; 
    fmt.fmt.pix.height      = video_res->height;
    fmt.fmt.pix.pixelformat = video_res->format;
    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

    if (-1 == xioctl (socam_data.camera_fd, VIDIOC_S_FMT, &fmt))
    {
    	printf("failed set resolution\n");
		return	SOERR_FAILED_SET_RES;
    }
    return SOERR_NONE;
}

SOERR c270_run(VideoRes *res, FN_CAPTURE_CALLBACK fn_capture, FN_ERROR_CALLBACK fn_error, void* user_data)
{
	enum 	v4l2_buf_type 		type;
	struct v4l2_capability 		cap;
	struct v4l2_requestbuffers  req;
	int i = 0;
	SOERR ret = SOERR_FAILED_START_VIDEO_CAP;
		
	if(!is_file_exist(socam_data.device_file_name))
	{
		printf("the c270 device is not been pluged=%d\n", __LINE__);
		socam_data.status = SOCAM_STATUS_UNPLUGED;
		return SOERR_NO_CAM_DEVICE;
	}
	
	if(res == NULL)
	{
		return SOERR_INVALID_PARAM;
	}
	
    socam_data.camera_fd = open(socam_data.device_file_name, O_RDWR);
	if(socam_data.camera_fd <= 0)
	{
		printf("c270 device can not be opened!\n");
		goto error;
	}
	
	ret = vc_set_res(res);
	if(ret != SOERR_NONE)
	{
		goto error;
	}

	if (-1 == xioctl (socam_data.camera_fd, VIDIOC_QUERYCAP, &cap))
	{
		printf("ioctl VIDIOC_QUERYCAP failed\n");
		goto error;
	}
	
    if(!((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) && (cap.capabilities & V4L2_CAP_STREAMING)))
    {
        printf ("no video capture device\n");
        goto error;
    }

    memset(&req, 0, sizeof(struct v4l2_requestbuffers));
	req.count               = 4;
	req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory              = V4L2_MEMORY_MMAP;

	if (-1 == xioctl (socam_data.camera_fd, VIDIOC_REQBUFS, &req)) 
	{
		if(EINVAL == errno)
		{
			 printf("does not support memory mapping\n");
		}
		printf("VIDIOC_REQBUFS\n");
		goto error;
    }

    if (req.count < 2) 
    {
        printf("Insufficient buffer memory\n");
        goto error;
    }
	socam_data.camera_buffer_num = req.count;
    socam_data.camera_buffers = (SoV4l2Buffer*)calloc (socam_data.camera_buffer_num, sizeof(SoV4l2Buffer));
	
    if (!socam_data.camera_buffers) 
    {
        printf("Out of memory\n");
        goto error;
    }

    for (i = 0; i < socam_data.camera_buffer_num; ++i) 
    {
        struct v4l2_buffer buf;

        memset(&buf, 0, sizeof(struct v4l2_buffer));

        buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory      = V4L2_MEMORY_MMAP;
        buf.index       = i;

        if (-1 == xioctl (socam_data.camera_fd, VIDIOC_QUERYBUF, &buf))
        {
            printf("VIDIOC_QUERYBUF\n");
            goto error;
		}
		
        socam_data.camera_buffers[i].length = buf.length;
        socam_data.camera_buffers[i].start = mmap (NULL /* start anywhere */,
										  buf.length,
										  PROT_READ | PROT_WRITE /* required */,
										  MAP_SHARED /* recommended */,
										  socam_data.camera_fd, buf.m.offset);

        if (MAP_FAILED == socam_data.camera_buffers[i].start)
        {
        	socam_data.camera_buffers[i].length = 0;
           	printf("mmap failed\n");
			goto error;
        }
    }
    
	for (i = 0; i < socam_data.camera_buffer_num; ++i) 
	{
		struct v4l2_buffer buf;

		memset(&buf, 0, sizeof(struct v4l2_buffer));

		buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory      = V4L2_MEMORY_MMAP;
		buf.index       = i;

		if (-1 == xioctl (socam_data.camera_fd, VIDIOC_QBUF, &buf))
		{
			printf("VIDIOC_QBUF failed\n");
			goto error;
		}
	}
		
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (-1 == xioctl (socam_data.camera_fd, VIDIOC_STREAMON, &type))
	{
		printf("VIDIOC_STREAMON failed\n");
		goto error;
	}
	
	socam_data.fn_capture = fn_capture;
	socam_data.fn_error = fn_error;
	socam_data.user_data = user_data;
	
	pthread_create(&socam_data.video_capture_id,NULL,video_capture_thread,NULL);

	return SOERR_NONE;
	
error:
	if(socam_data.camera_fd>0)
	{
		close(socam_data.camera_fd);
	}
	socam_data.camera_fd = 0;
	
	return ret;
}

SOERR c270_stop()
{
	if(socam_data.init_id != INIT_ID)
	{
		return SOERR_UNINITED;
	}
	
	if(socam_data.status != SOCAM_STATUS_RUNNING)
	{
		return SOERR_WRONG_STATUS;
	}
	
	socam_data.status = SOCAM_STATUS_STOPPED;
	
	usleep(30);
	pthread_join(socam_data.video_capture_id, NULL);
	
	if(socam_data.camera_fd>0)
    {
		close(socam_data.camera_fd);
		socam_data.camera_fd = 0;	
    }
	
	return SOERR_NONE;
}

SOCAM_STATUS c270_get_status()
{
	if(!is_file_exist(socam_data.device_file_name))
	{
		printf("the c270 device is not been pluged=%d\n", __LINE__);
		return SOCAM_STATUS_UNPLUGED;
	}
	
	return socam_data.status;	
}

SOERR c270_uninit()
{	
	if(socam_data.init_id != INIT_ID)
		return SOERR_UNINITED;
	if(socam_data.status == SOCAM_STATUS_RUNNING)
	{
		c270_stop();
	}

	memset(&socam_data, 0, sizeof(socam_data));
	
	return SOERR_NONE;
}

#define MAX_SUPPORT_RES 128
static VideoRes 	s_video_res[MAX_SUPPORT_RES];
static VideoResList svideo_res_list;

SOERR c270_list_res(VideoResList** res_list)
{
	struct v4l2_frmsizeenum size;
    struct v4l2_fmtdesc fmt;
    SOERR ret = SOERR_FAILED_LIST_RES;
    
    int camera_fd = 0;
	
    if(res_list == NULL)
	{
		LOGI("Error in Fun:%s, Line:%d", __func__, __LINE__);
		return SOERR_INVALID_PARAM;
	}
		
	svideo_res_list.num_res = 0;
	*res_list = &svideo_res_list;

	if(socam_data.init_id != INIT_ID)
	{
		LOGI("Error in Fun:%s, Line:%d", __func__, __LINE__);
		return SOERR_UNINITED;
	}
		
	if(socam_data.status == SOCAM_STATUS_RUNNING && socam_data.camera_fd >0)
	{
		LOGI("Error in Fun:%s, Line:%d", __func__, __LINE__);
		return SOERR_WRONG_STATUS;
	}

	
	if(!is_file_exist(socam_data.device_file_name))
	{
		LOGI("the c270 device is not been pluged=%d, %s\n", __LINE__, socam_data.device_file_name);
		return SOERR_NO_CAM_DEVICE;
	}
	
	camera_fd = open(socam_data.device_file_name, O_RDWR);
	if(camera_fd <=0)
	{
		LOGI("open %s failed::Error in Fun:%s, Line:%d", socam_data.device_file_name, __func__, __LINE__);
		return SOERR_FAILED_LIST_RES;
	}
	
	memset(&svideo_res_list, 0, sizeof(VideoResList));
	svideo_res_list.video_res = s_video_res;
	
    fmt.index = 0;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if((xioctl (camera_fd, VIDIOC_ENUM_FMT, &fmt)<0))
    {
    	LOGI("the c270 device file ioctl failed.\n");
     	goto exit;
    }
    memset (&size, 0, sizeof(struct v4l2_frmsizeenum));
    size.index = 0;
    size.pixel_format = fmt.pixelformat;
    if((xioctl (camera_fd, VIDIOC_ENUM_FRAMESIZES, &size)<0))
    {
        LOGI("can not list the frame size!\n");
     	goto exit;
    }
    if (size.type == V4L2_FRMSIZE_TYPE_DISCRETE)
    {
        do
        {
            s_video_res[size.index].width = size.discrete.width;
            s_video_res[size.index].height = size.discrete.height;
			s_video_res[size.index].format = size.pixel_format;
			
            LOGI("got discrete frame size %dx%d, format: 0x%x\n",
                   size.discrete.width,
                   size.discrete.height,
                   size.pixel_format);
            size.index++;
            svideo_res_list.num_res++;
            if(size.index>=MAX_SUPPORT_RES)
            {
                break;
            }
        } while (xioctl(camera_fd, VIDIOC_ENUM_FRAMESIZES, &size) >= 0);
        ret = SOERR_NONE;
    }
    else
    {
        LOGI("known type!\n");
        ret = SOERR_FAILED_LIST_RES;
    }
exit:

	if(svideo_res_list.num_res == 0)
	{
		LOGI("Error in Fun:%s, Line:%d", __func__, __LINE__);
		return SOERR_FAILED_LIST_RES;
	}
	*res_list = &svideo_res_list;
	
	if(camera_fd>0)
		close(camera_fd);
		
	return ret;
}






